package org.kisekiproject.course {
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.utils.Dictionary;
	
	import org.kisekiproject.evaluator.IExpression;
	import org.kisekiproject.evaluator.IVariableNamespace;
	import org.kisekiproject.evaluator.events.VariableChangeEvent;
	import org.kisekiproject.events.KisekiEvent;
	import org.kisekiproject.instance.Instance;

	/**
	 * Holds variables that can be simple variables, expressions or late binding objects.
	 */
	public class CourseVariableStore extends EventDispatcher implements IVariableNamespace {

		private var _expressions:Dictionary;
		private var _nameByExpression:Dictionary;
		private var _instances:Dictionary;
		private var _groups:Dictionary;
		private var _queryFunction:CourseQueryFunction;
		private var _course:CourseModel;

		/**
		 * Constrcutor.
		 */
		public function CourseVariableStore() {
			_expressions=new Dictionary();
			_nameByExpression=new Dictionary();
			_instances=new Dictionary();
			_groups=new Dictionary();
		}

		/**
		 * Set coursemodel reference.
		 */
		public function set course(value:CourseModel):void {
			_course=value;
		}

		/**
		 * Set an expression to use as variable.
		 */
		public function setVariableExpression(name:String, expression:IExpression):void {
			ensureNotUsed(name);
			
			_expressions[name]=expression;
			_nameByExpression[expression]=name;
			expression.addEventListener(Event.CHANGE,onExpressionChange);
			dispatchEvent(new VariableChangeEvent(VariableChangeEvent.VARIABLE_CHANGE,name));
		}

		/**
		 * Expression change.
		 */
		private function onExpressionChange(event:Event):void {
			var exp:IExpression=IExpression(event.target);
			var name:String=_nameByExpression[exp];
			
			dispatchEvent(new VariableChangeEvent(VariableChangeEvent.VARIABLE_CHANGE,name));
		}

		/**
		 * Set an instance to use as a variable.
		 */
		public function setInstanceVariable(instance:Instance):void {
			ensureNotUsed(instance.id);

			_instances[instance.id]=instance;
			instance.addEventListener(KisekiEvent.OUTPUT_CHANGE,onInstanceOutputChange);
		}

		/**
		 * Output change.
		 */
		protected function onInstanceOutputChange(event:Event):void {
			var instance:Instance=Instance(event.target);

			dispatchEvent(new VariableChangeEvent(VariableChangeEvent.VARIABLE_CHANGE,instance.id));
		}
		
		/**
		 * Ensure the name is not used.
		 */
		private function ensureNotUsed(name:String):void {
			if (_expressions[name]!==undefined)
				throw new Error("Variable name is already used: "+name);

			if (_instances[name]!==undefined)
				throw new Error("Variable name is already used: "+name);
		}

		/**
		 * Get variable.
		 */
		public function getVariable(name:String):Object {
			if (name=="$")
				return new CourseQueryFunction(_course);

			if (_expressions[name]!==undefined)
				return IExpression(_expressions[name]).value;

			else if (_instances[name]!==undefined)
				return Instance(_instances[name]).object;

			else if (_groups[name]!==undefined)
				return CourseGroup(_groups[name]).exposed;

			else
				throw new Error("Variable not defined: "+name);
		}

		/**
		 * Set a group variable.
		 */
		public function setGroupVariable(group:CourseGroup):void {
			ensureNotUsed(group.id);

			_groups[group.id]=group;

			group.addEventListener(CourseGroup.CONTENT_CHANGE,onGroupContentChange);
		}

		/**
		 * Content change.
		 */
		private function onGroupContentChange(event:Event):void {
			dispatchEvent(new VariableChangeEvent(VariableChangeEvent.VARIABLE_CHANGE,CourseGroup(event.target).id));
		}

		/**
		 * Get variable names.
		 */
		public function get variableNames():Array {
			var a:Array=new Array();

			for (var s:String in _expressions)
				a.push(s);
			
			return a;
		}
	}
}